﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace VsTortoiseSVN
{
    public static class PathHelper
    {
        private static StringBuilder _tempBuffer = new StringBuilder(Win32.MAX_PATH);

        /// <summary>
        /// The ToLongPathNameToShortPathName function retrieves the short path form of a specified long input path
        /// </summary>
        /// <param name="longName">The long name path</param>
        /// <returns>A short name path string</returns>
        public static string GetShortPathName(string longPath)
        {
            if (String.IsNullOrEmpty(longPath))
                return longPath;

            Win32.GetShortPathName(longPath, _tempBuffer, (uint)_tempBuffer.Capacity);
            return _tempBuffer.ToString();
        }

        /// <summary>
        /// Converts a short path to a long path.
        /// </summary>
        /// <param name="shortPath">A path that may contain short path elements (~1).</param>
        /// <returns>The long path.  Null or empty if the input is null or empty.</returns>
        public static string GetLongPathName(string shortPath)
        {
            if (String.IsNullOrEmpty(shortPath))
                return shortPath;

            Win32.GetLongPathName(shortPath, _tempBuffer, _tempBuffer.Capacity);
            return _tempBuffer.ToString();
        }

        static string AddTrailingChar(string text, char c)
        {
            if (text.Length > 0 && text[text.Length - 1] == c)
                return text; // char exists at end of text already

            return text + c;
        }

        /// <summary>
        /// Gets whether sub is a sub-directory of root.
        /// </summary>
        public static bool IsSubDirectory(string root, string sub)
        {
            if (sub.IndexOf(AddTrailingChar(root, Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase) > -1)
                return true;

            if (sub.IndexOf(AddTrailingChar(root, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase) > -1)
                return true;

            return false;
        }

        public static bool CollapseDirectories(out string parentDirectory, List<string> paths)
        {
            List<string> directories = new List<string>(paths);

            // Step 1a: Extract directory-paths of each provided file-path
            //
            // Input:
            //   C:\Projects\App
            //   C:\Projects\App\Source\main.h
            //   C:\Projects\App\Source\main.cpp
            //
            // Output:
            //   C:\Projects\App
            //   C:\Projects\App\Source\
            //   C:\Projects\App\Source\
            //
            for (int n = 0; n < directories.Count; ++n)
            {
                if(File.Exists(directories[n]))
                    directories[n] = Path.GetDirectoryName(directories[n]);
            }

            // Step 1b: Remove sub-directories of output from above
            //
            // Input:
            //   C:\Projects\App
            //   C:\Projects\App\Source\
            //   C:\Projects\App\Source\
            //
            // Output:
            //   C:\Projects\App
            //
            RemoveSubDirectories(directories);
            if (directories.Count == 1)
            {
                // That was an easy one, directory found
                parentDirectory = directories[0];
                return true;
            }

            // We are here when the list could not get collapsed to one directory,
            // this happens in scenarios like the following:
            //
            // Input:
            //   C:\Projects\App\Header\
            //   C:\Projects\App\Source\
            //   C:\Projects\Framework\Source\
            //
            // In this case we have to find the deepest shared directory
            // of all elements in the list, which is 'C:\Projects'.
            //
            // In order to find it, we substitute during each iteration
            // the parent directory of the first element in the list and then remove all sub directories.
            //
            // 1st iteration:
            //   C:\Projects\App\Header\
            //   C:\Projects\App\Source\
            //   C:\Projects\Framework\Source\
            //   C:\Projects\App\                 <-- substituted
            //
            // 2nd interation:
            //   C:\Projects\App\
            //   C:\Projects\Framework\Source\
            //   C:\Projects\                     <-- substituted
            //
            // After the second iteration, only 'C:\Projects' is left and that's what we are looking for.
            //
            int retriesLeft = 40; // equals directory depth
            while (directories.Count > 1)
            {
                string parent = Directory.GetParent(directories[0]).FullName;
                directories.Add(parent);
                RemoveSubDirectories(directories);

                // If for some reason it's not possible to collapse these directories,
                // make sure not to loop forever.
                if (--retriesLeft <= 0)
                    break;
            }

            if (directories.Count == 1)
            {
                parentDirectory = directories[0];
                return true;
            }

            parentDirectory = string.Empty;
            return false;
        }

        /// <summary>
        /// Removes  sub-directories from directories.
        /// </summary>
        public static void RemoveSubDirectories(List<string> directories)
        {
            List<string> source = new List<string>(directories);
            RemoveSubDirectories(directories, source);
        }

        /// <summary>
        /// Removes sub-directories from source and stores the
        /// result of the operation in dest.
        /// </summary>
        public static void RemoveSubDirectories(List<string> dest, ICollection<string> source)
        {
            dest.Clear();
            dest.AddRange(source);

            for (int i = dest.Count-1; i >= 0; --i)
            {
                for (int j = i - 1; j >= 0; --j)
                {
                    if (string.Compare(dest[j], dest[i], true) == 0)
                    {
                        dest.RemoveAt(i);
                        break;
                    }

                    if (IsSubDirectory(dest[j], dest[i]))
                    {
                        dest.RemoveAt(i);
                        break;
                    }

                    if (IsSubDirectory(dest[i], dest[j]))
                    {
                        dest.RemoveAt(j);
                        break;
                    }
                }
            }
        }
    }
}
